#include "glplatform.h"
#include "glplatform-glcore.h"

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

bool fullscreen = false;
float speed = 0;
float pos[3] = { 0, 0, -0.25};
float heading[3] = { 0, 0, 1};

void on_key_down(struct glplatform_win *win, int k)
{
	//TODO: No fullscreen support for windows yet
#ifndef _WIN32
	if (k == 'f') {
		fullscreen = !fullscreen;
		glplatform_fullscreen_win(win, fullscreen);
	}
#endif
	if (k == 'w') {
		speed = 1.0;
		heading[0] = 0;
		heading[1] = 0;
		heading[2] = 1;
	} else if (k == 's') {
		speed = -1.0;
		heading[0] = 0;
		heading[1] = 0;
		heading[2] = 1;
	} else if (k == 'a') {
		speed = -1.0;
		heading[0] = 1;
		heading[1] = 0;
		heading[2] = 0;
	} else if (k == 'd') {
		speed = 1.0;
		heading[0] = 1;
		heading[1] = 0;
		heading[2] = 0;
	}
}

void on_key_up(struct glplatform_win *win, int k)
{
	speed = 0.0;
}

void on_destroy(struct glplatform_win *win)
{
	glplatform_destroy_window(win);
}

int main()
{
	struct glplatform_win_callbacks cb = {
		.on_destroy = on_destroy,
		.on_key_down = on_key_down,
		.on_key_up = on_key_up
	};
	if (!glplatform_init()) {
		exit(-1);
	}
	struct glplatform_win *win = glplatform_create_window("Hello window", &cb, NULL, 512, 512);
	if (!win)
		exit(-1);

	glplatform_show_window(win);
	glplatform_gl_context_t ctx = glplatform_create_context(win, 3, 3);
	if (!ctx)
		exit(-1);
	glplatform_make_current(win, ctx);
	if (!glplatform_glcore_init(3, 3)) {
		exit(-1);
	}

	GLuint vao;
	GLuint vbo[2];
	GLuint vib;

	glGenVertexArrays(1, &vao);
	glBindVertexArray(vao);

	glGenBuffers(2, vbo);

	float verts[6*6][3] = {
		{-1,-1, -1}, { 1,-1, -1}, { 1, 1, -1}, {-1,-1, -1}, { 1, 1, -1}, {-1, 1, -1},
		{-1,-1, 1}, { 1,-1, 1}, { 1, 1, 1}, {-1,-1, 1}, { 1, 1, 1}, {-1, 1, 1},

		{-1,-1,-1}, {-1, -1, 1}, {1, -1, 1}, {-1, -1,-1}, {1, -1,1}, {1, -1, -1},
		{-1,1,-1}, {-1, 1, 1}, {1, 1, 1}, {-1, 1,-1}, {1, 1,1}, {1, 1, -1},

		{-1,-1,-1}, {-1, 1, -1}, {-1, 1, 1}, {-1,-1, -1}, {-1,1,1}, {-1,-1,1},
		{1,-1, -1}, { 1, 1, -1}, {1, 1, 1}, {1,-1, -1}, {1, 1, 1}, {1,-1,1}
	};

	float normals[6*6][3] = {
		{0, 0, 1}, {0, 0, 1}, {0, 0, 1}, {0, 0, 1}, {0, 0, 1}, {0, 0, 1},
		{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1},

		{0, 1,0}, {0, 1,0}, {0, 1,0}, {0, 1,0}, {0, 1,0}, {0, 1,0},
		{0, -1,0}, {0, -1,0}, {0, -1,0}, {0, -1,0}, {0, -1,0}, {0, -1,0},

		{1,0,0}, {1,0,0}, {1,0,0}, {1,0,0}, {1,0,0}, {1,0,0},
		{-1,0,0}, {-1,0,0}, {-1,0,0}, {-1,0,0}, {-1,0,0}, {-1,0,0},
	};

	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 6 * 3, verts, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 6 * 3, normals, GL_STATIC_DRAW);

	GLuint vs = glCreateShader(GL_VERTEX_SHADER);
	GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
	GLuint program = glCreateProgram();

	const char *vs_text =
		"#version 330\n"
		"in vec3 pos;\n"
		"in vec3 normal;\n"
		"out vec3 fs_pos;\n"
		"out vec3 fs_normal;\n"
		"uniform mat4 mvp;\n"
		"uniform vec3 offset;\n"
		"void main()\n"
		"{\n"
		"	fs_normal = normal;\n"
		"	gl_Position = mvp * vec4(pos + offset, 1);\n"
		"}\n";

	const char *fs_text =
		"#version 330\n"
		"out vec4 color;\n"
		"in vec3 fs_normal;\n"
		"void main()\n"
		"{\n"
		"	color = vec4(vec3(0.5) + (fs_normal/2), 1);\n"
		"}\n";

	glShaderSource(vs, 1, &vs_text, NULL);
	glShaderSource(fs, 1, &fs_text, NULL);
	glCompileShader(vs);
	glCompileShader(fs);

	char info[1024];
	glGetShaderInfoLog(vs, 1024, NULL, info);
	printf("VS log %s\n", info);
	glGetShaderInfoLog(fs, 1024, NULL, info);
	printf("FS log %s\n", info);

	glAttachShader(program, vs);
	glAttachShader(program, fs);
	glLinkProgram(program);
	glUseProgram(program);
	glEnableVertexAttribArray(glGetAttribLocation(program, "pos"));
	glEnableVertexAttribArray(glGetAttribLocation(program, "normal"));

	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glVertexAttribPointer(glGetAttribLocation(program, "pos"),
			3,
			GL_FLOAT,
			GL_FALSE,
			0,
			0);
	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glVertexAttribPointer(glGetAttribLocation(program, "normal"),
			3,
			GL_FLOAT,
			GL_FALSE,
			0,
			0);

	glEnable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);

	while (glplatform_process_events()) {
		float ar = win->width * 1.0f/win->height;
		float zNear = 0.05;
		float zFar = 3.00;
		float zRange = zNear - zFar;
		const float tanHalfFOV = tanf(M_PI * (0.5 / 2.0));

		float mvp[4][4];

		mvp[0][0] = 1.0f / (tanHalfFOV * ar);
		mvp[0][1] = 0.0f;
		mvp[0][2] = 0.0f;
		mvp[0][3] = 0.0f;

		mvp[1][0] = 0.0f;
		mvp[1][1] = 1.0f / tanHalfFOV;
		mvp[1][2] = 0.0f;
		mvp[1][3] = 0.0f;

		mvp[2][0] = 0.0f;
		mvp[2][1] = 0.0f;
		mvp[2][2] = (-zNear - zFar) / zRange;
		mvp[2][3] = (2.0f * zFar * zNear / zRange);

		mvp[3][0] = 0.0f;
		mvp[3][1] = 0.0f;
		mvp[3][2] = 1.0f;
		mvp[3][3] = 0.0f;

		glViewport(0, 0, win->width, win->height);

		glUniformMatrix4fv(glGetUniformLocation(program,"mvp"), 1, GL_TRUE, &mvp[0][0]);
		glUniform3f(glGetUniformLocation(program,"offset"), -pos[0], -pos[1], -pos[2]);

		glClearColor(0,0,1,1);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glDrawArrays(GL_TRIANGLES, 0, 6 * 6 * 3);
		glplatform_swap_buffers(win);

		int i;
		for (i = 0; i < 3; i++) {
			pos[i] += heading[i] * speed * 0.01;
		}

		if (glplatform_get_events(true) < 0)
			break;
	}
}
